# -*- coding: utf-8 -*-
import sys

import ctypes
from ctypes import *
from ctypes.util import find_library

libamico = None

class ListPOINTER(object):
    '''Just like a POINTER but accept a list of ctype as an argument.
    Used to pass arrays in to the C++ functions'''
    def __init__(self, etype):
        self.etype = etype

    def from_param(self, param):
        if isinstance(param, (list, tuple)):
#            print "Py: IS INSTANCE"
            return (self.etype * len(param))(*param)
        else:
#            print "Py: NOT INSTANCE"
            return param
            
def initAmiCo():
    """Initialization function. Sets up types of return values of the functions that
    we will use in our program"""
    global libamico #using global variable
    #function 'setSizeOfArrays' takes a C pointer and a tuple
    libamico.setSizeOfArrays.argtypes = [c_void_p, ListPOINTER(c_int)]
    #function callFloatArrayMethod returns py_object. In Python it will be a simple tuple
    libamico.callFloatArrayMethod.restype = py_object
    #function callStringMethod returns string
    libamico.callStringMethod.restype = ctypes.c_char_p
    #function callFloatMethod returns a float pointing number
    libamico.callFloatMethod.restype = ctypes.c_float

def FuncPrintHelloUniverse():
    """Calls method that prints a simple string 'Hello Universe!!!'"""
    #use global variable
    global libamico
    #take a method ID
    helloUniverse = libamico.getMethod("printHelloUniverse", "()V")
    #method doesn't return any value so we use a 'callVoidMethod' function
    libamico.callVoidMethod(helloUniverse)
    
def FuncPrintCustomString(str):
    """Passes a string in to the Java method"""
    global libamico
    #take a method ID
    printCustomString = libamico.getMethod("printCustomString", "(Ljava/lang/String;)V")
    #function callVoidMethod takes a C pointer and a string as parameters
    libamico.callVoidMethod.argtypes = [ctypes.c_void_p,  c_char_p]
    #method doesn't return any value so we use a 'callVoidMethod' function
    libamico.callVoidMethod(printCustomString, str)
    
def FuncPassArrayOfIntegers():
    """Calls the method that takes an array of integers as parameter and 
    prints this array to the screen"""
    array = [1, 2, 3]
    #take a method ID
    passArrayOfIntegers = libamico.getMethod("passArrayOfIntegers", "([I)V")
    #function callVoidMethod takes a C pointer and a Python tuple
    libamico.callVoidMethod.argtypes = [c_void_p,  ListPOINTER(c_int)]
    #cause there is an array in parameters we need to set it's size
    #we pass 1 array with length 3
    libamico.setSizeOfArrays(1, (3,))
    #method doesn't return any value so we use a 'callVoidMethod' function
    libamico.callVoidMethod(passArrayOfIntegers, tuple(array))
    
def FuncGetArrayOfFloats():
    """Calls a method that will return an array of floats"""
    #take a method ID
    getArrayOfFloats = libamico.getMethod("getArrayOfFloats", "()[F")
    #function callFloatArrayMethod takes only one
    #required argument -- ID of the method to be called
    libamico.callFloatArrayMethod.argtypes = [c_void_p]
    #function 'callFloatArrayMethod' will return
    #result of caling 'getArrayOfFloats' method,
    #i.e. an array of floats
    res = libamico.callFloatArrayMethod(getArrayOfFloats)
    print "result of 'getArrayOfFloats' method: ", res
    
def FuncGetIntAsString():
    """Calls method with parameter -- integer and takes a result of calling
    this method -- a string representation of the passed integer"""
    #take a method ID
    getIntAsString = libamico.getMethod("getIntAsString", "(I)Ljava/lang/String;")
    #takes a method ID and an integer number.
    #Type of returning value was changed in 'initAmiCo' function
    libamico.callStringMethod.argtypes = [c_void_p, c_int]
    #method returns a Sting value so we use 'callStringMethod' function
    res = libamico.callStringMethod(getIntAsString, 5)
    print "result of 'getIntAsString' method: ", res

def FuncSumOfFloatAndInt_AsFloat():
    """Calls a method that takes two parameters -- float pointing number and an
    integer number and returns a float pointing number -- sum of this two numbers"""
    sumOfFloatAndInt_AsFloat = libamico.getMethod("sumOfFloatAndInt_AsFloat", "(FI)F")
    #takes C pointer, double and int.
    #Yes, exactly double. When do you pass a float pointing number in to a method it
    #should have a type double casue of some C++ restrictions, but in Java method
    #should take a float
    libamico.callFloatMethod.argtypes = [c_void_p, c_double, c_int]
    #returns a float
    res = libamico.callFloatMethod(sumOfFloatAndInt_AsFloat, c_double(2.2), c_int(5))
    print "result of 'sumOfFloatAndInt_AsFloat' method: ", res
    
def passLong():
    """Passes in to a Java method maximal value of type long in Java and Python"""
    passLong = libamico.getMethod("passLong", "(J)V")
    #Java's and Python's long type is equivalent to long long or __int64 in C++
    libamico.callVoidMethod.argtypes = [c_void_p, c_longlong]
    libamico.callVoidMethod(passLong, 9223372036854775807)

def HelloUniverseMain():
    global libamico
    print sys.platform
    print "Py: AmiCo Simulation Started with library :",
    print sys.platform
    if (sys.platform == 'linux2'):
	##########################################
	# find_library on Linux could only be used if your libAmiCoPyJava.so is
	# on system search path or path to the library is added in to LD_LIBRARY_PATH
	#
	# name =  'AmiCoPyJava'
        # loadName = find_library(name)
        ##########################################
        loadName = './libAmiCoPyJava.so'
        libamico = ctypes.CDLL(loadName)
        print libamico
    else: #else if OS is a Mac OS X (libAmiCo.dylib is searched for) or Windows (AmiCo.dll)
        name =  'AmiCoPyJava'
        loadName = find_library(name)
        print loadName
        libamico = ctypes.CDLL(loadName)
        print libamico

    #create JVM without any additional startup options
    libamico.amicoInitialize(0)
    #set internal amico types (local function)
    initAmiCo()
    #load a class in to the JVM
    className = "HelloUniverse"
    libamico.loadJavaClass(className)
    
    FuncPrintHelloUniverse()
    FuncPrintCustomString("Custom string from Python")
    FuncPassArrayOfIntegers()
    FuncGetArrayOfFloats()
    FuncGetIntAsString()
    FuncSumOfFloatAndInt_AsFloat()
    passLong()
    
    #destroy a JVM
    libamico.destroyEnvironment()

if __name__ == "__main__":
    HelloUniverseMain()

